package Plants;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;

import SmartGrid.ConsumerEnergyMeter;
import SmartGrid.EnergyMarket;
import SmartGrid.ExecutionEngine;
import SmartGrid.SmartGrid;
import Tasks.IncrementElectricOutput;

/**
 * Base class for all power plants.  
 * All power plants are aware of their neighboring plants in the graph
 * Each plant produces a certain amount of energy (electricOutput)
 * and has a certain amount of available energy to use (availableEnergy)
 * Available energy can be less than electric output if this power plant is sending electricity to another plant
 * Available energy can be more than electric output if this power plant is receiving electricity from another plant
 * @author jeffrey
 *
 */
public class PowerPlant {
	
	Map<String, PowerLine> powerLines;
	List<ConsumerEnergyMeter> consumers;
	
	String name;
	int electricOutput;  	//how much energy this power plant is producing on its own
	int availableEnergy; 	//how much energy is available to distribute to consumers
							//if this plant is helping another plant, available energy is
							//less than electric output.  IF this plant is receiving help,
							//available energy is greater than electric output
	int responsibilities;	//how much energy this power plant needs to have available for its consumers
	int maxOutput;
	int funds;
	
	List<PowerPlantListener> listeners;
	
	//Drawing references
	Point location;
	Rectangle boundingBox;
	boolean highlighted;
	boolean visited;
	
	public PowerPlant()
	{
		powerLines = new HashMap<String, PowerLine>();
		consumers = new ArrayList<ConsumerEnergyMeter>();
		name = "";
		electricOutput = 0;
		availableEnergy = 0;
		maxOutput = 1000;
		funds = 1000;
		
		highlighted = false;
		visited = false;
		
		listeners = new ArrayList<PowerPlantListener>();
		
		Random rand = new Random();
		location = new Point(rand.nextInt(Tools.Options.CANVAS_WIDTH - Tools.Options.PLANT_MARKER_SIZE), 
				rand.nextInt(Tools.Options.CANVAS_HEIGHT - Tools.Options.PLANT_MARKER_SIZE));
		boundingBox = new Rectangle(location.x, location.y, Tools.Options.PLANT_MARKER_SIZE, Tools.Options.PLANT_MARKER_SIZE);
	}
	
	public PowerPlant(String name)
	{
		this();
		this.name = name;
	}
	
	public void paint(Graphics g)
	{
		if(availableEnergy == responsibilities)
		{
			g.setColor(Color.BLUE);
			g.fillOval(location.x, location.y, Tools.Options.PLANT_MARKER_SIZE, Tools.Options.PLANT_MARKER_SIZE);
		}
		else if(availableEnergy > responsibilities)
		{
			g.setColor(Color.GREEN);
			g.fillOval(location.x, location.y, Tools.Options.PLANT_MARKER_SIZE, Tools.Options.PLANT_MARKER_SIZE);
		}
		else 
		{
			g.setColor(Color.RED);
			g.fillOval(location.x, location.y, Tools.Options.PLANT_MARKER_SIZE, Tools.Options.PLANT_MARKER_SIZE);
		}
		Iterator<PowerLine> lines = powerLines.values().iterator();
		while(lines.hasNext())
		{
			PowerLine line = lines.next();
			g.drawLine(location.x + Tools.Options.PLANT_MARKER_SIZE/2, location.y + Tools.Options.PLANT_MARKER_SIZE/2,
					line.getEndNode(this).location.x + Tools.Options.PLANT_MARKER_SIZE/2,
					line.getEndNode(this).location.y + Tools.Options.PLANT_MARKER_SIZE/2);
		}
		if(isHighlighted())
		{
			g.drawRect(boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height);
			String display = getName() + " Responsibilities: " + getResponsibilities() + " Available Energy: " + getAvailableEnergy() + " Electric Output: " + getElectricOutput();
			g.drawString(display, (int)getLocation().getX(), (int)getLocation().getY());
		}
			
	}
	
	/**
	 * Connects a second power plant to this power plant.  
	 * Creates a power line between them
	 * @param plant
	 */
	public void addPowerPlant(PowerPlant plant, float distance)
	{
		PowerLine link = new PowerLine(this, plant);
		link.setDistance(distance);
		powerLines.put(plant.getName(), link);
		plant.getPowerLines().put(this.getName(), link);
	}
	
	public PowerLine getLineWith(PowerPlant plant)
	{
		return powerLines.get(plant.getName());
	}
	
	public PowerLine getLineWith(String name)
	{
		return powerLines.get(name);
	}
	
	public Map<String, PowerLine> getPowerLines()
	{
		return powerLines;
	}
	
	public List<ConsumerEnergyMeter> getConsumers()
	{
		return consumers;
	}
	
	public int getExcessEnergy()
	{
		return getAvailableEnergy() - getResponsibilities();
	}
	
	/**
	 * Calculates the nearest power plant which has an excess energy level of energy.
	 * Returns null if there are no such power plant exists
	 * Use in conjunction with GraphAlgorithms.GetPath to find the path to this power plant
	 * @param energy
	 * @return
	 */
	public PowerPlant getNearestNeighborWithEnergy(int energy)
	{
		PowerPlant nearest = null;
		float shortestDistance = Float.POSITIVE_INFINITY;
		
		Iterator<PowerPlant> iter = SmartGrid.allPlants.values().iterator();
		while(iter.hasNext())
		{
			PowerPlant p = iter.next();
			if(p.getExcessEnergy() >= energy)
			{
				float distance = GraphAlgorithms.getShortestDistance(this, p); 
				if(distance < shortestDistance)
				{
					nearest = p;
					shortestDistance = distance;
				}
			}
		}
		return nearest;
	}
	
	/**
	 * Adds a consumer and updates this power plant's responsibilities
	 * @param consumer
	 */
	public void addConsumer(ConsumerEnergyMeter consumer)
	{
		getConsumers().add(consumer);
		setResponsibilities(getResponsibilities() + consumer.getEnergyDemand(), true);
	}
	
	/**
	 * Sets the responsibility for this power plant and triggers the canvas's repaint
	 * @param responsibility
	 */
	public void setResponsibilities(int responsibility, boolean notify)
	{
		responsibilities = responsibility;
		if(notify)
		{
			for(int i = 0; i < listeners.size(); i++)
			{
				listeners.get(i).PowerPlantResponsibilitiesChanged(this);
			}
		}
	}
	
	public int getResponsibilities()
	{
		return responsibilities;
	}
	
	public int getElectricOutput()
	{
		return electricOutput;
	}
	
	public void setElectricOutput(int output, boolean notify)
	{
		this.electricOutput = output;
		if(notify)
		{
			for(int i = 0; i < listeners.size(); i++)
			{
				listeners.get(i).PowerPlantSupplyChanged(this);
			}
		}
	}
	
	/**
	 * Increments both this plant's electric output as well as its available energy
	 * @param increment
	 */
	public void incrementElectricOutput(int increment, boolean notify)
	{
		setElectricOutput(getElectricOutput() + increment, false);
		incrementAvailableEnergy(increment, false);
		EnergyMarket.supply.getAndAdd(increment);
		if(notify)
		{
			for(int i = 0; i < listeners.size(); i++)
			{
				listeners.get(i).PowerPlantSupplyChanged(this);
			}
		}
	}
	
	public int getAvailableEnergy()
	{
		return availableEnergy;
	}
	
	public void setAvailableEnergy(int energy, boolean notify)
	{
		availableEnergy = energy;
		if(notify)
		{
			for(int i = 0; i < listeners.size(); i++)
			{
				listeners.get(i).PowerPlantSupplyChanged(this);
			}
		}
	}	
	
	/**
	 * Increments this plants available energy without increasing its current output
	 * This can happen if this plant is receiving energy from another plant
	 * @param increment
	 */
	public void incrementAvailableEnergy(int increment, boolean notify)
	{
		setAvailableEnergy(getAvailableEnergy() + increment, false);
		if(notify)
		{
			for(int i = 0; i < listeners.size(); i++)
			{
				listeners.get(i).PowerPlantSupplyChanged(this);
			}
		}
	}
	
	public List<PowerPlantListener> getListeners()
	{
		return listeners;
	}
	
	public String getName()
	{
		return name;
	}
	
	public Rectangle getBoundingBox()
	{
		return boundingBox;
	}
	
	public Point getLocation()
	{
		return location;
	}
	
	public boolean isHighlighted() {
		return highlighted;
	}

	public void setHighlighted(boolean highlighted) {
		this.highlighted = highlighted;
		for(int i = 0; i < listeners.size(); i++)
		{
			listeners.get(i).PowerPlantRepaint(this);
		}
	}
	
	public boolean isVisited() {
		return visited;
	}

	public void setVisited(boolean visited) {
		this.visited = visited;
	}
	
	/*Execution Methods*/

	public void sendEnergy(PowerPlant destination, int amount)
	{
		PowerLine line = getLineWith(destination);
		if(line == null) return;
		line.sendEnergy(this, destination, amount);
	}
	
	/**
	 * Issues a task for the execution engine
	 * Steadily raises electric output of this plant over time
	 * @param power
	 */
	public void raiseElectricOutput(int cutoff)
	{
		IncrementElectricOutput rp = new IncrementElectricOutput(this, cutoff);
		ExecutionEngine.addRecurringTask(rp, Tools.Options.INCREMENT_ELECTRIC_OUTPUT_DELAY);
	}
	
	@Override
	public String toString()
	{
		String content;
		if(powerLines.size() > 0)
		{
			content = name + " is connected to:\n";
			for(int i = 0; i < powerLines.size(); i++)
			{
				content += powerLines.get(i).getEndNode(this).getName();
				content += "\n";
			}
		}
		else
		{
			content = name + " is not connected\n";
		}
		return content;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		PowerPlant other = (PowerPlant) obj;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}

}
